#include <iostream>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#include <sys/select.h>
#include "Sock.hpp"

int main()
{
    uint16_t port = (uint16_t)8080;
    int listen_socket = Sock::Socket();
    Sock::Bind(listen_socket, port);
    Sock::Listen(listen_socket);
    std::cout << "listen 函数调用完毕，开始检测文件描述符是否有事件到来..." << std::endl;
    //到这里就要accept吗？不应该
    // accept需要从listen_socket获取链接；
    // accept函数不知道listen_socket是否有链接到来，那么直接调用accept
    //就需要阻塞等待listen_socket有链接到来；
    //在select的视角，链接到来，意味着listen_socket的读事件就绪

#define NUM (sizeof(fd_set) * 8) // 1024

    /*该数组值大于等于0就是有对应的fd,-1就是无fd*/
    int fd_array[NUM]; //存储文件描述符的数组
    for (int i = 0; i < NUM; i++)
    {
        fd_array[i] = -1;
    }

    fd_set rfds;
    fd_array[0] = listen_socket; //值为 listen_socket = 3;
    for (;;)
    {
        FD_ZERO(&rfds); //每次调用select之前，需要清空读集合的检测文件描述符

        //开始给rfds添加文件描述符，

        int max_fd = fd_array[0];
        //遍历数组，寻找合法的fd,添加到rfds位图中
        for (int i = 0; i < NUM; i++)
        {
            if (fd_array[i] == -1)
                continue;
            //碰到合法的文件描述符
            FD_SET(fd_array[i], &rfds); //将合法的文件描述符添加到rfds中
            //更新最大值的文件描述符值
            if (fd_array[i] > max_fd)
                max_fd = fd_array[i];
        }
        std::cout << "提前保存要检测的文件描述符到数组中，同时将保存在数组中的文件描述符设置到rfds,让select函数去检测...." << std::endl;
        struct timeval tv = {5, 0}; //超时时间
        //服务器上所有的fd（监听套接字和通信套接字）都需要交给select去检测是否有事件就绪
        /*
            那么这里就会面临一个问题：我们的监听套接字可以在select函数调用之前
            就设置好了，可以交给select函数去检测，但是我们的通信套接字是在select函数
            和accept函数调用之后才会有的呀，那么如何把我们的accept返回的通信套接字
            也设置为让select函数去检测是否有读事件就绪呢？这是需要我们处理和思考的；

        */
        int n = select(max_fd + 1, &rfds, NULL, NULL, NULL); //一旦rfds有读事件就绪，那么就会返回
        //一但select调用成功，返回到给用户就绪事件的文件描述符
        //用户就可以拿到这些就绪文件描述符，进行IO函数的操作了
        //这样，IO函数就不需要进行阻塞等待了，而是进行真正的读写操作了
        std::cout << "select 函数调用成功，正在检测是否有文件描述符就绪..." << std::endl;
        switch (n)
        {
        case -1:
            std::cerr << "select 函数调出错..." << std::endl;
            break;
        case 0:
            std::cerr << "select函数调用超时..." << std::endl;
            break;
        default:
            std::cout << "有对应的文件描述符fd事件就绪了，但是我还不知道是哪一个fd就绪，所以我需要自己判断一下..." << std::endl;

            // select函数检测到有事件就绪了，但是我们并不知道是哪一个，所以我们要对其进行检测
            //对谁进行检测？当然是rfds,函数走到这里，就表明rfds是有就绪事件的了，但是还不知道哪一个

            //遍历历史交给select函数检测的fd，看看是否有事件就绪了
            for (int i = 0; i < NUM; i++)
            {
                if (fd_array[i] == -1)
                    continue;
                //到这里肯定是合法fd，但是不一定是就绪的，所以我们要对其合法的fd进行检测
                if (FD_ISSET(fd_array[i], &rfds))
                {
                    std::cout << "检测到文件描述符：" << fd_array[i] << "，存储在数组[" << i << "]位置"
                              << " 有读事件就绪..." << std::endl;
                    //来到这里肯定是该要检测的fd读事件就绪了
                    //那么此时，如果进行IO操作，就不会再阻塞了

                    //但是我们不知道就绪的文件描述符是谁的，所以我们还要判断
                    if (fd_array[i] == listen_socket)
                    {
                        std::cout << "检测到文件描述符: " << fd_array[i] << " 是监听监听套接字读事件就绪,可以直接处理..." << std::endl;
                        // accept
                        int sock = Sock::Accept(listen_socket);
                        if (sock >= 0) //该通信文件描述符，还需要加到数组中，并且设置到rfds,让select函数去检测是否有事件就绪到来
                        {
                            //寻找要被检测的文件描述符的数组的空位置
                            //放入新到来的通信文件描述符
                            int pos = 1;
                            for (; pos < NUM; pos++)
                            {
                                if (fd_array[pos] == -1)
                                    break;
                            }
                            // for结束，两种情况
                            // 1.无位置可以放入信的通信描述符
                            // 2.找到一个位置放入新文件描述符
                            if (pos < NUM)
                            {
                                fd_array[pos] = sock;
                                std::cout << "给数组添加新的要检测的通信文件描述符：" << fd_array[pos] << "，存储在数组[" << pos << "]位置" << std::endl;
                            }
                            else
                            {
                                //无位置可以放入新通信文件描述符
                                //此时说明服务器满载啦，无法对新来文件描述符进行操作了
                                close(sock);
                            }
                        }
                    }
                    else
                    {
                        //通信描述符读事件就绪了
                        //可以进行IO操作，一定不会阻塞
                        /*但是哦，这里也会有问题，你能保证你一次可以缓冲区的数据吗
                         */
                        char buf[1024];
                        ssize_t sz = recv(fd_array[i], buf, sizeof(buf) - 1, 0);
                        if (sz > 0)
                        {
                            buf[sz] = '\0';
                            std::cout << "client[" << fd_array[i] << "]: " << buf << std::endl;
                        }
                        else if (sz == 0)
                        {
                            /*注意对端关闭连接：也是一种读事件就绪*/
                            //对端关闭了链接，那么我们也要关闭该链接呗
                            close(fd_array[i]);
                            //单单关闭还不行，因为该文件描述符，还是在fd_array中
                            //也就是说，即使你关

                            //闭了链接，下次select去检测时候，依旧会检测到你关闭的链接,那么select就会直接报错

                            //所以我们要去掉该文件描述符在数组的位置
                            fd_array[i] = -1;
                        }
                        else
                        {
                            close(fd_array[i]);
                            fd_array[i] = -1;
                        }
                    }
                }
            }
            break;
        }
    }
    return 0;
}
